package com.sikaryofficial.backend.manager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.sikaryofficial.backend.config.RedeemRuleConfig;
import com.sikaryofficial.backend.constant.ActivityConstant;
import com.sikaryofficial.backend.constant.PrizeGrantStatusEnum;
import com.sikaryofficial.backend.constant.PrizeRedeemBusinessTypeEnum;
import com.sikaryofficial.backend.constant.PrizeTypeEnum;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeAddressReq;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeListReq;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeOwnerListReq;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeRedeemListReq;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeRedeemReq;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeRedeemUpdateObj;
import com.sikaryofficial.backend.domain.dto.req.prize.PrizeReq;
import com.sikaryofficial.backend.domain.dto.resp.AddressUserListDTO;
import com.sikaryofficial.backend.domain.dto.resp.prize.PrizeDTO;
import com.sikaryofficial.backend.domain.dto.resp.prize.PrizeRedeemDTO;
import com.sikaryofficial.backend.domain.entity.ActivityDelivery;
import com.sikaryofficial.backend.domain.entity.ActivityPrizeRelation;
import com.sikaryofficial.backend.domain.entity.ActivityResult;
import com.sikaryofficial.backend.domain.entity.PointsBalance;
import com.sikaryofficial.backend.domain.entity.Prize;
import com.sikaryofficial.backend.domain.entity.PrizeRedeem;
import com.sikaryofficial.backend.domain.entity.UserAddress;
import com.sikaryofficial.backend.domain.mapping.PrizeMapping;
import com.sikaryofficial.backend.resolver.PropertiesTools;
import com.sikaryofficial.backend.service.IActivityDeliveryService;
import com.sikaryofficial.backend.service.IActivityPrizeRelationService;
import com.sikaryofficial.backend.service.IActivityResultService;
import com.sikaryofficial.backend.service.IAttachmentService;
import com.sikaryofficial.backend.service.IPointsBalanceService;
import com.sikaryofficial.backend.service.IPointsDetailStrategy;
import com.sikaryofficial.backend.service.IPrizeRedeemService;
import com.sikaryofficial.backend.service.IPrizeService;
import com.sikaryofficial.backend.service.IUserAddressService;
import com.sikaryofficial.backend.service.IUserCertificateService;
import com.sikaryofficial.backend.service.email.notice.GuildTaskNoticeService;
import com.sikaryofficial.backend.service.statistics.MyStatisticsProcessor;
import com.sikaryofficial.common.core.constant.PointRuleConstant;
import com.sikaryofficial.common.core.domain.R;
import com.sikaryofficial.common.core.exception.ServiceException;
import com.sikaryofficial.common.redis.lock.RedisDistributedLocker;
import com.sikaryofficial.common.security.utils.SecurityUtils;
import com.sikaryofficial.system.api.RemoteUserService;
import com.sikaryofficial.system.api.domain.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.sikaryofficial.backend.constant.ActivityConstant.HIDE_YES;
import static com.sikaryofficial.backend.constant.RedisCacheKey.ACTIVITY_WINNER_EXPORT_LOCK;
import static com.sikaryofficial.backend.constant.i18n.ExceptionConstant.*;

/**
 * @author : qinjinyuan
 * @desc : 活动管理服务
 * @date : 2023/10/30 15:39
 */
@Service
@Slf4j
@SuppressWarnings("unchecked")
public class PrizeManager {
    @Autowired
    protected RedisDistributedLocker locker;
    @Autowired
    private IPrizeService prizeService;
    @Autowired
    private IPointsBalanceService pointsBalanceService;
    @Resource
    private PropertiesTools propertiesTools;
    @Resource
    private Map<String, IPointsDetailStrategy> pointsDetailStrategyMap;
    @Resource
    private IActivityPrizeRelationService relationService;
    @Autowired
    private IPrizeRedeemService prizeRedeemService;
    @Autowired
    private IAttachmentService attachmentService;
    @Resource
    private RemoteUserService remoteUserService;
    @Autowired
    private IUserAddressService usAddressService;
    @Autowired
    private IUserCertificateService userCertificateService;
    @Autowired
    private MyStatisticsProcessor myListener;
    @Autowired
    private IUserAddressService userAddressService;
    @Autowired
    private IActivityDeliveryService activityDeliveryService;
    @Autowired
    private IActivityResultService activityResultService;
    @Autowired
    private GuildTaskNoticeService guildTaskNoticeService;
    @Resource
    private RedeemRuleConfig redeemRuleConfig;

    /**
     * 分页查询-只查询实物礼品
     *
     * @param req 请求入参
     * @return 分页查询结果
     */
    public IPage<PrizeDTO> listPageForH5(PrizeListReq req) {
        Page<Prize> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        IPage<Prize> result = prizeService.page(customerPage, new LambdaQueryWrapper<Prize>()
                .select(Prize::getPrizeId, Prize::getPrizeName, Prize::getPrizeBrand, Prize::getPrizeCover, Prize::getPoints)
                .eq(Prize::getDeletedVersion, 0L)
                .eq(Prize::getPrizeType, PrizeTypeEnum.PHYSICAL.getCode())
                .eq(Prize::getHasHide, ActivityConstant.HIDE_NO)
                .orderByDesc(Prize::getCreatedTime, Prize::getUpdatedTime));
        if (CollUtil.isEmpty(result.getRecords())) {
            return new Page<>(1, 10, 0);
        }
        IPage<PrizeDTO> prizeDTOIPage = result.convert(PrizeMapping.INSTANCE::coverToDTO);
        // 查询当前用户积分
        PointsBalance finalPointsBalance = getPointsBalance(SecurityUtils.getUserId());
        prizeDTOIPage.getRecords().forEach(item -> item.setHasRedeem(isHasRedeem(item.getPoints(), item.getPrizeStock(), finalPointsBalance)));
        return prizeDTOIPage;
    }

    public IPage<PrizeRedeemDTO> ownerRedeemList(PrizeOwnerListReq req) {
        if (Objects.isNull(req.getUserId()) || Objects.equals(req.getUserId(), 0L)) {
            return new Page<>(1, 7, 0);
        }
        Page<PrizeRedeemDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        IPage<PrizeRedeemDTO> prizeRedeemIPage = prizeRedeemService.ownerRedeemList(customerPage, req);
        if (CollUtil.isEmpty(prizeRedeemIPage.getRecords())) {
            return new Page<>(1, 7, 0);
        }
        List<PrizeRedeemDTO> prizeRedeemDTOS = prizeRedeemIPage.getRecords();
        buildRedeemExtInfo(prizeRedeemDTOS);
        return prizeRedeemIPage;
    }

    /**
     * 兑奖(奖品)详情
     *
     * @param objectId
     * @param businessType
     * @return
     */
    public PrizeRedeemDTO ownerRedeemDetail(Integer businessType, Long objectId) {
        if (Objects.equals(SecurityUtils.getUserId(), 0L)) {
            return null;
        }
        PrizeRedeemDTO prizeRedeemDTO;
        // 如果是 积分兑换则走下面的逻辑
        PrizeRedeemBusinessTypeEnum businessTypeEnum = PrizeRedeemBusinessTypeEnum.getByCode(businessType);
        switch (businessTypeEnum) {
            case REDEEM:
                prizeRedeemDTO = prizeRedeemService.getOwnerRedeemDetail(objectId);
                break;
            case LOTTERY_TURNTABLE:
            case LOTTERY_SCRATCH:
            case POINTS_RESERVATION:
                prizeRedeemDTO = prizeRedeemService.getOwnerRedeemDetailByResultId(objectId);
                break;
            default:
                throw new ServiceException("The shipping address does not exist.");
        }
        if (prizeRedeemDTO != null) {
            buildRedeemExtInfo(Lists.newArrayList(prizeRedeemDTO));
        }
        return prizeRedeemDTO;
    }

    public IPage<PrizeRedeemDTO> redeemListPage(PrizeRedeemListReq req) {
        if (Objects.isNull(req.getPrizeId()) || Objects.equals(req.getPrizeId(), 0L)) {
            return new Page<>(1, 7, 0);
        }
        Page<PrizeRedeemDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());
        // 查询实物礼品
        req.setPrizeType(PrizeTypeEnum.PHYSICAL.getCode());
        // 只显示兑奖数据
        req.setBusinessType(PrizeRedeemBusinessTypeEnum.REDEEM.getCode());
        IPage<PrizeRedeemDTO> prizeRedeemIPage = prizeRedeemService.prizeRedeemPageList(customerPage, req);
        if (CollUtil.isEmpty(prizeRedeemIPage.getRecords())) {
            return new Page<>(1, 7, 0);
        }
        List<PrizeRedeemDTO> prizeRedeemDTOS = prizeRedeemIPage.getRecords();
        // 补充其他信息
        buildOtherInfo(prizeRedeemDTOS);
        return prizeRedeemIPage;
    }

    private void buildOtherInfo(List<PrizeRedeemDTO> prizeRedeemDTOS) {
        // 补充用户相关信息
        Map<Long, AddressUserListDTO> addressUserListDTOMap = usAddressService.getAddressListByIds(
                prizeRedeemDTOS.stream().map(PrizeRedeemDTO::getUserAddressId).collect(Collectors.toList()));

        List<Long> userIds = prizeRedeemDTOS.stream().map(PrizeRedeemDTO::getCreatedBy).collect(Collectors.toList());
        // 21岁 证明数据查询
        Map<Long, String> proofMap = userCertificateService.get18thProofMap(new HashSet<>(userIds));
        // 填充用户个人相关信息
        for (PrizeRedeemDTO item : prizeRedeemDTOS) {
            String url = MapUtils.getString(proofMap, item.getCreatedBy(), null);
            item.setProofUrl(url);
            item.setHasProofUrl(Objects.nonNull(url));
            AddressUserListDTO addressObj = MapUtils.getObject(addressUserListDTOMap, item.getUserAddressId());
            if (Objects.isNull(addressObj)) {
                continue;
            }
            item.setFirstName(addressObj.getFirstName());
            item.setPhoneNumber(addressObj.getPhoneNumber());
            item.setLastName(addressObj.getLastName());
            item.setPostCode(addressObj.getPostCode());
            item.setAddressObj(addressObj);
        }
        // 填充用户个人昵称和邮箱
        for (PrizeRedeemDTO item : prizeRedeemDTOS) {
            R<SysUser> result1 = remoteUserService.getUserByUserId(item.getCreatedBy());
            if (Objects.isNull(result1) || Objects.isNull(result1.getData())) {
                continue;
            }
            SysUser sysUser = result1.getData();
            item.setNickName(sysUser.getNickName());
            item.setEmail(sysUser.getEmail());
        }
    }

    /**
     * 兑换奖品信息填充
     *
     * @param prizeRedeemDTOS
     */
    private void buildRedeemExtInfo(List<PrizeRedeemDTO> prizeRedeemDTOS) {
        List<Long> prizeRedeemIds = prizeRedeemDTOS.stream().map(PrizeRedeemDTO::getPrizeRedeemId).collect(Collectors.toList());
        List<ActivityDelivery> activityDeliveryList = activityDeliveryService.lambdaQuery().eq(ActivityDelivery::getDeletedVersion, 0L)
                .in(ActivityDelivery::getObjectId, prizeRedeemIds)
                .list();
        Map<Long, ActivityDelivery> activityDeliveryMap = activityDeliveryList.stream().collect(Collectors.toMap(ActivityDelivery::getObjectId, Function.identity(), (k1, k2) -> k1));
        // 填充奖品名称
        for (PrizeRedeemDTO item : prizeRedeemDTOS) {
            ActivityDelivery activityDelivery = MapUtils.getObject(activityDeliveryMap, item.getPrizeRedeemId());
            if (Objects.isNull(activityDelivery)) {
                continue;
            }
            item.setAddressObj(activityDelivery.getAddressObj());
            AddressUserListDTO addressObj = activityDelivery.getAddressObj();
            if (Objects.nonNull(addressObj)) {
                item.setFirstName(addressObj.getFirstName());
                item.setPhoneNumber(addressObj.getPhoneNumber());
                item.setLastName(addressObj.getLastName());
                item.setPostCode(addressObj.getPostCode());
            }
        }
    }

    /**
     * 礼品分页查询列表
     *
     * @param req             分页请求对象
     * @param excludePrizeIds 被排除的礼品ID
     * @return 分页对象
     */
    public IPage<PrizeDTO> listPage(PrizeListReq req, List<Long> excludePrizeIds) {
        Page<PrizeDTO> customerPage = new Page<>(req.getPageNum(), req.getPageSize());

        IPage<PrizeDTO> prizeDTOIPage = prizeService.selectPrizeByPage(customerPage, req, excludePrizeIds);
        if (CollUtil.isEmpty(prizeDTOIPage.getRecords())) {
            return new Page<>(1, 10, 0);
        }
        List<Long> prizeIds = prizeDTOIPage.getRecords().stream().map(PrizeDTO::getPrizeId).collect(Collectors.toList());
        // 查询关联活动
        Set<Long> relationsPrizeIdSet = relationService.getRelationPrizeId(prizeIds);
        prizeDTOIPage.getRecords().forEach(item -> item.setHasRelation(relationsPrizeIdSet.contains(item.getPrizeId())));
        return prizeDTOIPage;
    }

    /**
     * 分页查询
     *
     * @param req 请求入参
     * @return 分页查询结果
     */
    public IPage<PrizeDTO> listPage(PrizeListReq req) {
        return listPage(req, Lists.newArrayList());
    }

    /**
     * 获取奖品详情
     *
     * @param prizeId 奖品ID
     * @return 查询对象
     */
    public PrizeDTO getInfoForH5(Long prizeId) {
        Prize prize = prizeService.getOne(new LambdaQueryWrapper<Prize>().select(Prize::getPrizeId,
                        Prize::getPrizeName, Prize::getPrizeBrand, Prize::getPrizeCover,
                        Prize::getPoints, Prize::getPrizeFunction, Prize::getPrizeStock, Prize::getHasCash, Prize::getPrizeType)
                .eq(Prize::getPrizeId, prizeId).eq(Prize::getDeletedVersion, 0L));
        if (Objects.isNull(prize)) {
            return null;
        }
        PrizeDTO result = PrizeMapping.INSTANCE.coverToDTO(prize);
        PointsBalance pointsBalance = getPointsBalance(SecurityUtils.getUserId());
        result.setHasRedeem(isHasRedeem(result.getPoints(), result.getPrizeStock(), pointsBalance));
        result.setAttachmentList(attachmentService.getAttachmentById(prizeId));
        // 礼品兑换数量
        Map<Long, Integer> map = prizeRedeemService.prizeRedeemSignCount(prizeId, PrizeRedeemBusinessTypeEnum.REDEEM.getCode());
        result.setRedeemCount(MapUtils.getInteger(map, prizeId, 0));
        return result;
    }

    private boolean isHasRedeem(Integer points, Integer prizeStock, PointsBalance pointsBalance) {
        if (Objects.isNull(pointsBalance) || Objects.isNull(points) || Objects.isNull(prizeStock)
                || Objects.isNull(pointsBalance.getBalance())) {
            return false;
        }
        return pointsBalance.getBalance() >= points && prizeStock >= 1;
    }

    /**
     * 根据当前用户ID获取对应用户积分余额
     *
     * @return 积分余额
     */
    private PointsBalance getPointsBalance(Long userId) {
        // 查询当前用户积分
        PointsBalance pointsBalance = pointsBalanceService.getPointsBalanceByUserId(userId);
        if (Objects.isNull(pointsBalance) || Objects.isNull(pointsBalance.getBalance())) {
            pointsBalance = new PointsBalance();
            pointsBalance.setBalance(0L);
        }
        return pointsBalance;
    }

    /**
     * 获取奖品详情
     *
     * @param prizeId 奖品ID
     * @return 奖品对象
     */
    public PrizeDTO getInfo(Long prizeId) {
        Prize prize = prizeService.getOne(new LambdaQueryWrapper<Prize>()
                .eq(Prize::getPrizeId, prizeId).eq(Prize::getDeletedVersion, 0L));
        if (Objects.isNull(prize)) {
            return null;
        }
        PrizeDTO result = PrizeMapping.INSTANCE.coverToDTO(prize);
        result.setAttachmentList(attachmentService.getAttachmentById(prizeId));
        Map<Long, Integer> map = prizeRedeemService.prizeRedeemSignCount(prizeId, PrizeRedeemBusinessTypeEnum.REDEEM.getCode());
        result.setRedeemCount(MapUtils.getInteger(map, prizeId, 0));
        return result;
    }

    /**
     * 新增或修改
     *
     * @param req 保存对象
     * @return 主键ID
     */
    public Long saveOrUpdate(PrizeReq req) {
        // 虚拟礼品，所需积分大于0的判定
        if (Objects.isNull(req.getPoints()) || req.getPoints() <= 0) {
            throw new ServiceException("礼品所需积分必须大于0");
        }
        // 当hasCash = 1 时，奖品类型只能是实体礼品
        if (req.getHasCash() == 1 && !PrizeTypeEnum.PHYSICAL.getCode().equals(req.getPrizeType())) {
            throw new ServiceException("只有实体礼品才可以设置现金支付");
        }
        Prize prize = PrizeMapping.INSTANCE.coverReqToEntity(req);
        if (Objects.isNull(prize.getPrizeId())) {
            prize.setPrizeId(IdWorker.getId());
            prize.setCreatedBy(SecurityUtils.getUserId());
            prize.setCreatedTime(new Date());
        } else {
            // 如果 该礼品被引用，不能调整礼品类型
            // 如何判断是否被引用
            int relationSize = relationService.lambdaQuery().eq(ActivityPrizeRelation::getPrizeId, prize.getPrizeId())
                    .eq(ActivityPrizeRelation::getDeletedVersion, 0L).list().size();
            int redeemSize = prizeRedeemService.lambdaQuery().eq(PrizeRedeem::getPrizeId, prize.getPrizeId())
                    .eq(PrizeRedeem::getDeletedVersion, 0L).list().size();
            Prize sourcePrize = prizeService.getById(prize.getPrizeId());
            if (relationSize + redeemSize > 0 && !sourcePrize.getPrizeType().equals(prize.getPrizeType())) {
                throw new ServiceException("礼品已被关联，不能修改礼品类型");
            }
            prize.setUpdatedBy(SecurityUtils.getUserId());
            prize.setUpdatedTime(new Date());
        }
        prizeService.saveOrUpdate(prize);
        // 保存礼品附件
        attachmentService.deleteAttachmentList(prize.getPrizeId(), SecurityUtils.getUserId());
        attachmentService.saveAttachmentList(prize.getPrizeId(), req.getAttachmentList(), SecurityUtils.getUserId());
        return prize.getPrizeId();
    }

    /**
     * 删除
     *
     * @param prizeIds 被删除ID
     */
    public void remove(String prizeIds) {
        // 把 prizeIds 转为List<Long> 数据
        // 删除数据，进行关联校验
        commonRemovePreprocessing(prizeIds, list -> {
            List<Long> prizeIdList = list.stream().map(Prize::getPrizeId).collect(Collectors.toList());
            prizeService.update(new LambdaUpdateWrapper<Prize>().eq(Prize::getDeletedVersion, 0L).in(Prize::getPrizeId, prizeIdList).setSql(" deleted_version = prize_id "));
        });
    }

    private void commonRemovePreprocessing(String items, Consumer<List<Prize>> additionalProcessing) {
        String[] ids = items.split(",");
        List<Long> itemIdList = Lists.newArrayList();
        for (String id : ids) {
            itemIdList.add(Long.valueOf(id));
        }
        if (itemIdList.size() > 10) {
            throw new ServiceException("delete id size is bigger than 10");
        }
        Set<Long> relationsPrizeIdSet = relationService.getRelationPrizeId(itemIdList);
        if (CollUtil.isNotEmpty(relationsPrizeIdSet)) {
            throw new ServiceException("该礼品已经被关联使用，无法删除");
        }
        // 查询是否有兑换记录
        List<PrizeRedeem> prizeRedeems = prizeRedeemService.list(new LambdaQueryWrapper<PrizeRedeem>()
                .eq(PrizeRedeem::getDeletedVersion, 0L)
                .in(PrizeRedeem::getPrizeId, itemIdList)
        );
        if (CollUtil.isNotEmpty(prizeRedeems)) {
            throw new ServiceException("该礼品存在兑换记录，无法删除");
        }
        // 查询ID集合对应的数据
        List<Prize> list = prizeService.list(new LambdaQueryWrapper<Prize>().select(Prize::getPrizeId).eq(Prize::getDeletedVersion, 0L).in(Prize::getPrizeId, itemIdList));
        additionalProcessing.accept(list);
        // 执行额外的处理逻辑
    }

    /**
     * 礼品兑换
     * Attempts to redeem a prize by user.
     *
     * @param req The ID of the prize.
     * @return true if redemption is successful, false otherwise.
     */
    @Transactional(rollbackFor = ServiceException.class, propagation = Propagation.REQUIRED)
    public Boolean redeem(PrizeRedeemReq req) {
        if (Objects.isNull(req.getUserAddressId()) || req.getUserAddressId() <= 0L) {
            throw new ServiceException(propertiesTools.getProperties(PARAM_ERROR));
        }
        // 设置锁key
        String lockKey = MessageFormat.format(ACTIVITY_WINNER_EXPORT_LOCK, SecurityUtils.getUserId().toString());
        if (locker.tryLock(lockKey, TimeUnit.MINUTES, 1, 1)) {
            try {
                doRedeem(req);
            } catch (Exception e) {
                log.error("Redeem fail: {}", e);
                throw new ServiceException(e.getMessage());
            } finally {
                locker.unlock(lockKey);
            }
        } else {
            log.error("Get lock fail");
            throw new ServiceException("Redeem fail");
        }
        return true;
    }

    private void doRedeem(PrizeRedeemReq req) {
        /**
         * 每个用户每月最多兑换 3 次，且每个礼品每月只能兑换 1 次。
         */
        // 检查用户本月是否已兑换超过 3 次
        int monthlyTotal = redeemRuleConfig.getMonthlyTotal();
        if (!prizeRedeemService.canExchangeByUser(SecurityUtils.getUserId(), monthlyTotal)) {
            throw new ServiceException(String.format(propertiesTools.getProperties(PRIZE_REDEEM_TOTAL_LIMIT), monthlyTotal));
        }
        // 检查用户本月是否已兑换过该礼品
        int prizeLimitNum = redeemRuleConfig.getPrizeLimitNum();
        if (!prizeRedeemService.canExchangeByPrize(SecurityUtils.getUserId(), req.getPrizeId(), prizeLimitNum)) {
            throw new ServiceException(String.format(propertiesTools.getProperties(PRIZE_ONE_REDEEM_LIMIT), prizeLimitNum));
        }
        // 1、Verify user address and get address info
        AddressUserListDTO userAddressInfo = getUserAddressInfo(req.getUserAddressId());
        Long prizeId = req.getPrizeId();
        Integer prizeNum = req.getPrizeNum();
        // 2、Verify user has sufficient points
        PointsBalance pointsBalance = verifyUserPoints();
        // 3、Verify prize availability
        Prize prize = verifyPrizeAvailability(prizeId);
        // 4、Deduct points and inventory
        deductPointsAndInventory(prize, prizeNum, pointsBalance);
        // 5、Generate redemption record
        PrizeRedeem prizeRedeem = prizeRedeemService.prizeRedeemSave(prizeId, prizeNum, req.getUserAddressId(), PrizeGrantStatusEnum.YES.getCode());
        // 6、生成发货信息
        createDelivery(prizeRedeem.getPrizeRedeemId(), ActivityConstant.DELIVERY_SOURCE_PRIZE_REDEEM, userAddressInfo);
    }

    private AddressUserListDTO getUserAddressInfo(Long userAddressId) {
        UserAddress userAddress = usAddressService.getOne(new LambdaQueryWrapper<UserAddress>()
                .eq(UserAddress::getUserAddressId, userAddressId)
                .eq(UserAddress::getCreatedBy, SecurityUtils.getUserId())
                .eq(UserAddress::getDeletedVersion, 0L)
        );
        if (Objects.isNull(userAddress)) {
            throw new ServiceException(propertiesTools.getProperties(PARAM_ERROR));
        }
        // 获取地址详细信息
        Map<Long, AddressUserListDTO> userAddressMap = userAddressService.getAddressListByIds(Lists.newArrayList(userAddressId));
        AddressUserListDTO addressUserListDTO = MapUtils.getObject(userAddressMap, userAddressId);
        if (Objects.isNull(addressUserListDTO)) {
            throw new ServiceException("The shipping address does not exist.");
        }
        return addressUserListDTO;
    }

    private void validUserAddress(PrizeRedeemReq req) {
        // 地址数据校验
        UserAddress userAddress = usAddressService.getOne(new LambdaQueryWrapper<UserAddress>()
                .eq(UserAddress::getUserAddressId, req.getUserAddressId())
                .eq(UserAddress::getCreatedBy, SecurityUtils.getUserId())
                .eq(UserAddress::getDeletedVersion, 0L)
        );
        if (Objects.isNull(userAddress)) {
            throw new ServiceException(propertiesTools.getProperties(PARAM_ERROR));
        }
    }

    private PointsBalance verifyUserPoints() {
        PointsBalance pointsBalance = getPointsBalance(SecurityUtils.getUserId());
        if (pointsBalance.getBalance() <= 0) {
            throw new ServiceException(propertiesTools.getProperties(POINTS_NO_ENOUGH));
        }
        return pointsBalance;
    }

    private Prize verifyPrizeAvailability(Long prizeId) {
        Prize prize = prizeService.getOne(new LambdaQueryWrapper<Prize>()
                .eq(Prize::getPrizeId, prizeId)
                .eq(Prize::getDeletedVersion, 0L)
        );
        if (prize == null) {
            throw new ServiceException(propertiesTools.getProperties(PRIZE_NOT_EXIST));
        }
        // 如果是 隐藏礼品，则不支持兑换
        if (Objects.equals(HIDE_YES, prize.getHasHide())) {
            log.error("隐藏礼品，则不支持兑换");
            throw new ServiceException(propertiesTools.getProperties(PRIZE_NOT_EXIST));
        }
        PointsBalance pointsBalance = getPointsBalance(SecurityUtils.getUserId());
        // 库存和积分校验
        boolean isHasRedeem = !isHasRedeem(prize.getPoints(), prize.getPrizeStock(), pointsBalance);
        if (isHasRedeem) {
            throw new ServiceException(propertiesTools.getProperties(PRIZE_STOCK_OR_POINTS_ENOUGH));
        }
        return prize;
    }

    private void deductPointsAndInventory(Prize prize, Integer prizeNum, PointsBalance pointsBalance) {
        Integer totalNeedBalance = prize.getPoints() * prizeNum;
        IPointsDetailStrategy pointsDetailStrategy = pointsDetailStrategyMap.get(PointRuleConstant.GIFT_REDEEM + IPointsDetailStrategy.SUFFIX);

        if (totalNeedBalance > pointsBalance.getBalance()) {
            throw new ServiceException(propertiesTools.getProperties(POINTS_NO_ENOUGH));
        }
        // Deduct points
        pointsDetailStrategy.updatePointsByInputValue(prize.getPrizeId(), SecurityUtils.getUserId(), totalNeedBalance, PointRuleConstant.GIFT_REDEEM, prize.getPrizeName());
        // Deduct inventory
        prizeService.update(new LambdaUpdateWrapper<Prize>()
                .set(Prize::getPrizeStock, prize.getPrizeStock() - prizeNum)
                .eq(Prize::getPrizeId, prize.getPrizeId())
                .eq(Prize::getDeletedVersion, 0L));
    }

    /**
     * 关联转盘奖品地址
     *
     * @param req
     * @return
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void updateLotteryResultAddress(PrizeAddressReq req) {
        // 查询获奖结果数据，并生成发货数据
        PrizeRedeemDTO prizeRedeem = prizeRedeemService.getOwnerRedeemDetailByResultId(req.getPrizeRedeemId());
        if (Objects.isNull(prizeRedeem)) {
            throw new ServiceException("The shipping address does not exist or has been updated.");
        }
        if (PrizeRedeemBusinessTypeEnum.REDEEM.getCode().equals(prizeRedeem.getBusinessType())) {
            throw new ServiceException("Address updates that do not support point redemption.");
        }
        // 已发货列表
        if (Objects.nonNull(prizeRedeem.getUserAddressId())) {
            throw new ServiceException("The shipping address has been updated.");
        }
        // 虚拟奖品不更新收货地址
        if (PrizeTypeEnum.VIRTUALLY.getCode().equals(prizeRedeem.getPrizeType())
                || PrizeTypeEnum.THANK_YOU.getCode().equals(prizeRedeem.getPrizeType())) {
            throw new ServiceException("Virtual gifts cannot update addresses.");
        }
        // 如果是 积分兑换则走下面的逻辑
        PrizeRedeemBusinessTypeEnum businessTypeEnum = PrizeRedeemBusinessTypeEnum.getByCode(prizeRedeem.getBusinessType());
        switch (businessTypeEnum) {
            case LOTTERY_TURNTABLE:
            case LOTTERY_SCRATCH:
            case POINTS_RESERVATION:
                // 获取用户地址
                Map<Long, AddressUserListDTO> userAddressMap = getAddressData(req.getUserAddressId());
                AddressUserListDTO addressUserListDTO = MapUtils.getObject(userAddressMap, req.getUserAddressId());
                String email = Objects.isNull(addressUserListDTO) ? "" : addressUserListDTO.getEmail();
                if (CharSequenceUtil.isBlank(email)) {
                    throw new ServiceException("Email is required.");
                }
                // 中奖结果 (中奖结果列表展示地址)
                processLotteryAddress(prizeRedeem, req, userAddressMap);
                // 邮件发送
                if (prizeRedeem.getHasCash() == 0) {
                    guildTaskNoticeService.physicalGiftsNotice(email, prizeRedeem.getPrizeName());
                } else {
                    guildTaskNoticeService.userAccountNotice(email, prizeRedeem.getPrizeName());
                }
                break;
            default:
                throw new ServiceException("The shipping address does not exist.");
        }
    }

    private void processLotteryAddress(PrizeRedeemDTO prizeRedeem, PrizeAddressReq req, Map<Long, AddressUserListDTO> userAddressMap) {
        // 查询获奖结果数据，并生成发货数据
        ActivityResult activityResult = activityResultService
                .lambdaQuery()
                .eq(ActivityResult::getActivityResultId, req.getPrizeRedeemId())
                .eq(ActivityResult::getCreatedBy, req.getUserId())
                .eq(ActivityResult::getDeletedVersion, 0L)
                .one();
        if (Objects.isNull(activityResult)) {
            throw new ServiceException("The activity result data is empty");
        }
        // 已发货列表
        if (activityResult.getPrizeGrantStatus() != null && PrizeGrantStatusEnum.YES.getCode().equals(activityResult.getPrizeGrantStatus())) {
            throw new ServiceException("There is shipped data and cannot be updated");
        }
        createDelivery(userAddressMap, activityResult.getActivityResultId(), req.getUserAddressId(), ActivityConstant.DELIVERY_SOURCE_ACTIVITY);
        activityResultService.lambdaUpdate().eq(ActivityResult::getActivityResultId, activityResult.getActivityResultId())
                .set(ActivityResult::getPrizeGrantStatus, PrizeGrantStatusEnum.YES.getCode())
                .set(ActivityResult::getUpdatedBy, SecurityUtils.getUserId())
                .set(ActivityResult::getUpdatedTime, new Date())
                .set(ActivityResult::getUserAddressId, req.getUserAddressId()).update();
    }

    private void createDelivery(Map<Long, AddressUserListDTO> userDefaultAddressMap, Long objectId, Long userAddressId, Integer deliverySource) {
        AddressUserListDTO addressObj = MapUtils.getObject(userDefaultAddressMap, userAddressId);
        if (Objects.isNull(addressObj)) {
            throw new ServiceException("The user's default address is empty");
        }
        createDelivery(objectId, deliverySource, addressObj);
    }

    private void createDelivery(Long objectId, Integer deliverySource, AddressUserListDTO addressObj) {
        ActivityDelivery delivery = ActivityDelivery.builder()
                // 活动结果表ID或礼品ID
                .objectId(objectId)
                .activityDeliveryId(IdWorker.getId())
                .createdBy(SecurityUtils.getUserId())
                .createdTime(new Date())
                .addressObj(addressObj)
                .deliverySource(deliverySource)
                .build();
        activityDeliveryService.save(delivery);
    }

    private Map<Long, AddressUserListDTO> getAddressData(Long userAddressId) {
        // 创建一个 List 集合来存储 userAddressId
        List<Long> userAddressIds = new ArrayList<>();
        userAddressIds.add(userAddressId);
        // 查询默认地址
        Map<Long, AddressUserListDTO> userDefaultAddressMap = userAddressService.getAddressListByIds(userAddressIds);
        if (CollUtil.isEmpty(userDefaultAddressMap)) {
            throw new ServiceException("The shipping address does not exist.");
        }
        return userDefaultAddressMap;
    }

    public Boolean prizeRedeemListUpdate(List<PrizeRedeemUpdateObj> list) {
        List<Long> prizeRedeemIds = list.stream().map(PrizeRedeemUpdateObj::getPrizeRedeemId).collect(Collectors.toList());
        if (CollUtil.isEmpty(prizeRedeemIds)) {
            return false;
        }
        // 查询获奖结果数据，并生成发货数据
        List<PrizeRedeem> prizeRedeems = prizeRedeemService.list(new LambdaQueryWrapper<PrizeRedeem>()
                .eq(PrizeRedeem::getDeletedVersion, 0L)
                .in(PrizeRedeem::getPrizeRedeemId, prizeRedeemIds)
        );
        if (CollUtil.isEmpty(prizeRedeems)) {
            throw new ServiceException("仅支持积分兑换的发货处理");
        }
        // 已发货列表
        List<PrizeRedeem> grantedList = prizeRedeems.stream().filter(item -> item.getPrizeGrantStatus() != null && item.getPrizeGrantStatus().equals(1)).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(grantedList)) {
            throw new ServiceException("发货状态已变更");
        }
        // 获奖用户列表
        List<Long> userAddressIds = prizeRedeems.stream().map(PrizeRedeem::getUserAddressId).collect(Collectors.toList());
        // 查询默认地址
        Map<Long, AddressUserListDTO> userDefaultAddressMap = userAddressService.getAddressListByIds(userAddressIds);
        if (CollUtil.isEmpty(userDefaultAddressMap)) {
            throw new ServiceException("发货地址不存在");
        }
        Map<Long, Long> redeemAndUserAddressMap = prizeRedeems.stream()
                .collect(Collectors.toMap(PrizeRedeem::getPrizeRedeemId, PrizeRedeem::getUserAddressId, (k1, k2) -> k1));
        createDelivery(userDefaultAddressMap, redeemAndUserAddressMap);

        list.forEach(item -> prizeRedeemService.update(new LambdaUpdateWrapper<PrizeRedeem>()
                .eq(PrizeRedeem::getPrizeRedeemId, item.getPrizeRedeemId())
                .eq(PrizeRedeem::getDeletedVersion, 0L)
                .isNull(PrizeRedeem::getUserAddressId)
                .set(PrizeRedeem::getPrizeGrantStatus, item.getPrizeGrantStatus())
                .set(PrizeRedeem::getUpdatedBy, SecurityUtils.getUserId())
                .set(PrizeRedeem::getUpdatedTime, new Date())
        ));
        return true;
    }

    private void createDelivery(Map<Long, AddressUserListDTO> userDefaultAddressMap, Map<Long, Long> redeemAndUserAddressMap) {
        List<ActivityDelivery> deliveries = com.google.common.collect.Lists.newArrayList();
        redeemAndUserAddressMap.forEach((prizeRedeemId, userAddressId) -> {
            if (Objects.isNull(userAddressId)) {
                throw new ServiceException("The user's default address is empty");
            }
            AddressUserListDTO addressObj = MapUtils.getObject(userDefaultAddressMap, userAddressId);
            if (Objects.isNull(addressObj)) {
                throw new ServiceException("The user's default address is empty");
            }
            ActivityDelivery delivery = ActivityDelivery.builder()
                    .objectId(prizeRedeemId)
                    .activityDeliveryId(IdWorker.getId())
                    .createdBy(SecurityUtils.getUserId())
                    .createdTime(new Date())
                    .addressObj(addressObj)
                    .deliverySource(ActivityConstant.DELIVERY_SOURCE_PRIZE_REDEEM)
                    .build();
            deliveries.add(delivery);
        });
        activityDeliveryService.saveBatch(deliveries);
    }

    public Boolean prizeHidden(Long prizeId, Integer hide) {
        return prizeService.update(new LambdaUpdateWrapper<Prize>()
                .eq(Prize::getPrizeId, prizeId)
                .eq(Prize::getDeletedVersion, 0L)
                .set(Prize::getHasHide, hide)
                .set(Prize::getUpdatedBy, SecurityUtils.getUserId())
                .set(Prize::getUpdatedTime, new Date())
        );
    }
}
